﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using log4net;

namespace CSharpExt.CObject
{
    // ======================================================================
    // CObjectInfo
    // ======================================================================

    public class CObjectInfo
    {
        #region Private Data

        private Assembly _assembly;
        private Type _type;
        private String _name;

        #endregion

        #region Public Interface

        public CObjectInfo(Assembly assembly, Type type)
        {
            _assembly = assembly;
            _type = type;
            _name = type.Name;
        }

        public CObject Create(String objectName)
        {
            try
            {
                CObject cobject = (CObject)_assembly.CreateInstance(_type.FullName);
                cobject.setName(objectName);
                return cobject;
            }
            catch (Exception e)
            {
                LogManager.GetLogger(typeof(CObjectInfo)).ErrorFormat("FullName: {0} ImageRuntimeVersion: {1} Location: {2}", _assembly.FullName, _assembly.ImageRuntimeVersion, _assembly.Location);
                throw e;
            }
        }

        public Assembly AssemblyName
        {
            get { return _assembly; }
        }

        #endregion

        #region Properties

        public Type Type
        {
            get { return _type; }
        }

        public String Name
        {
            get { return _name; }
        }

        #endregion

        #region Implementation

        #endregion
    }

    public class CObjectFactory
    {
        #region Factory

        private static CObjectFactory _factory;

        public static CObjectFactory Create()
        {
            return Create(Directory.GetCurrentDirectory());
        }

        public static CObjectFactory Create(String directory)
        {
            _factory = new CObjectFactory(directory);
            return _factory;
        }

        public static CObject CreateObject(String objectType, String objectName)
        {
            if (_factory == null)
                Create();

            return _factory.createObject(objectType, objectName);
        }

        #endregion


        #region Private Data

        private static readonly ILog _logger = LogManager.GetLogger(typeof(CObjectFactory));

        private Dictionary<String, CObjectInfo> _availableObjects = new Dictionary<String, CObjectInfo>();

        #endregion

        #region Public Interface

        private CObjectFactory(String directory)
        {
            foreach (string assembly in Directory.GetFiles(directory, "*.dll"))
                processAssembly(assembly);
            processAssembly(Assembly.GetEntryAssembly());
        }

        private CObject createObject(String objectType, String objectName)
        {
            CObjectInfo coInfo;

            if (_availableObjects.TryGetValue(objectType, out coInfo))
            {
                CObject cobject = coInfo.Create(objectName);
                return cobject;
            }
            throw new InvalidOperationException(String.Format("CObject type {0} is not known.", objectType));
        }

        #endregion

        #region Implementation

        private void processAssembly(String assemblyName)
        {
            Assembly assembly;

            try
            {
                assembly = Assembly.LoadFile(assemblyName);

                Type[] types = assembly.GetTypes();
                foreach (Type type in types)
                {
                    if (type.IsClass)
                        processClass(assembly, type);
                }

            }
            catch (ReflectionTypeLoadException error)
            {
                _logger.WarnFormat("Can't load '{0}' assembly ('{1}').", assemblyName, error);
                foreach (Exception exception in error.LoaderExceptions)
                    _logger.Warn(exception.Message);
            }
            catch (BadImageFormatException)
            {
                // Ignore this exception. May be that working folder contains unamanged dll that must not be load as assemblies
            }
            catch (FileNotFoundException err)
            {
                _logger.WarnFormat("Can't load '{0}' assembly ('{1}').", assemblyName, err);
            }
            catch (Exception err)
            {
                _logger.WarnFormat("Can't load '{0}' assembly ('{1}').", assemblyName, err);
            }
        }

        private void processAssembly(Assembly assembly)
        {
            Type[] types = assembly.GetTypes();
            foreach (Type type in types)
            {
                if (type.IsClass)
                    processClass(assembly, type);
            }
        }

        private void processClass(Assembly assembly, Type type)
        {
            TypeNameAttribute attribute = (TypeNameAttribute) type.GetCustomAttribute(typeof(TypeNameAttribute), false);
            if (attribute == null)
                return;

            String typeName = type.Name;
            CObjectInfo coInfo;

            if (_availableObjects.TryGetValue(typeName, out coInfo))
            {
                _logger.WarnFormat("Cannot load CObject type {0} in assembly {1} because a type with the same name already exists in assembly {2}.", typeName, assembly.FullName, coInfo.AssemblyName);
            }
            else
            {
                _availableObjects.Add(type.Name, new CObjectInfo(assembly, type));
                _logger.InfoFormat("New ObjectType info loaded {0} - {1}.", type.Name, assembly.FullName);
            }
        }

        #endregion
    }

    internal static class ObjectHelper
    {
        internal static Attribute GetCustomAttribute(this Type type, Type attributeType)
        {
            return type.GetCustomAttribute(attributeType, true);
        }
        internal static Attribute GetCustomAttribute(this Type type, Type attributeType, bool inherit)
        {
            Attribute[] atts = (Attribute[]) type.GetCustomAttributes(attributeType, inherit);
            if (atts == null || atts.Length == 0)
                return null;
            else
                return atts[0];
        }
    }

}
